python与C/C++语言的交互

整合python高产与C/C++高效的优势
利用C或Python已有功能服务彼此
python作为胶水语言整合或者被整合到各类独立程序

扩展:python中调用C/C++编写的库

提升关键代码性能

引入C语言成熟功能库

以python为主程序,C通过.dll/.so形式使用

方式:

  • ctypes

    调用DLL或共享库的python功能函数库,标准库API

    https://docs.python.org/3.7/library/ctypes.html

    通过一个python标准库实现python扩展

    C语言功能编成.dll或.so库,加载库及调用函数,API

    C语言独立编程,python使用库调用接口函数

  • CFFI (C Foreign Function Interface for Python)

    在python中直接使用C函数的方式

    第三方库 pip install cffi

    https://cffi.readthedocs.io/en/latest/

    思路类似ctypes,使用API扩展C程序,也可以直接混合编程

    关注C函数的访问接口,而不是库函数,构建API

    C语言独立编程,python用CFFI扩展,学习成本低。

    • 功能接口
与C语言数据类型相关的接口 描述
ffi.NULL 相当于常量值NULL
ffi.new(cdecl) 数组或指针的生成,new(‘x*’)或new(‘x[n]’)
ffi.cast(ctype, value) C数据类型的声明,ctype是类型名,value是变量名 cast(‘int’, x)
ffi.string(cdata) 从cdata类型中返回一个Python字符串
ffi.unpack(cdata, length) 从cdata数组中获取特定长度,返回一个Python字符串或列表
与数据大小相关的接口 描述
ffi.typeof(ctype) 返回ctype的长度
ffi.sizeof(object) 返回object对象的长度
ffi.alignof(ctype) 返回ctype或对象的长度
与调用相关的接口 描述
ffi.dlopen(libpath) 打开动态链接库并建立一个句柄
ffi.dlclose(lib) 关闭动态链接库并释放句柄
ffi.cdef(str) str指明Python中需要使用的C类型、函数等声明
与内存操作相关的接口 描述
ffi.memmove(dst, src, n) 从src向dst拷贝n直接内容,src和dst都是python变量
  • 简单应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* 可编译为.dll的C语言代码  mxmul.h */
    #ifndef _DLL_H_
    #define _DLL_H_

    #if BUILDING_DLL
    #define DLLIMPORT __declspec(dllexport)
    #else
    #define DLLIMPORT __declspec(dllimport)
    #endif

    DLLIMPORT int *mxmul(int nrow, int nk, int ncol, int mx1[][nk], int mx2[][ncol]);

    #endif
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    /* 可编译为.dll的C语言代码  mxmul.c */
    #include"mxmul.h"
    #include<windows.h>
    #include<stdlib.h>

    /* Parameters: nrow, nk, ncol, mx1, mx2 */
    DLLIMPORT int *mxmul(int nrow, int nk, int ncol, int mx1[][nk], int mx2[][ncol])
    {
    int x, i, j;
    int *rst;
    rst = malloc(sizeof(int) * nrow * ncol);

    for(i = 0; i < nrow; i++)
    {
    for(j = 0; j < ncol; j++)
    {
    rst[i*ncol +j] =0;
    for(x = 0; x < nk; x++)
    {
    rest[i*ncol +j] +=*(*(mx1+i)+x) * *(*(mx2+x)+j);
    }
    }
    }
    return rst;
    }

    BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved)
    {
    switch(fdwReason)
    {
    case DLL_PROCESS_ATTACH:
    {
    break;
    }
    case DLL_PROCESS_DETACH:
    {
    break;
    }
    case DLL_THREAD_ATTACH:
    {
    break;
    }
    case DLL_THREAD_DETACH:
    {
    break;
    }
    }
    }

    mxmul.c和mxmul.h 编译后可以得到mxmul.dll和mxmul32.dll

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    # 用来封装.dll的python模块  cmxmul.py
    import array
    from cffi import FFI

    def cmxmul(nrow, nk, ncol, mx1, mx2):
    ffi = FFI()

    # 基本类型参数关联
    c_nrow = ffi.cast('int', nrow)
    c_nk = ffi.cast('int', nk)
    c_ncol = ffi.cast('int', ncol)

    # 列表类型参数关联
    _mx1 = array.array('l')
    _mx2 = array.array('l')
    [_mx1.fromlist(x) for x in mx1 ]
    [_mx2.fromlist(x) for x in mx2 ]

    # 数据的内存拷贝
    c_mx1 = ffi.new('int[]', len(_mx1))
    c_mx2 = ffi.new('int[]', len(_mx2))
    ffi.memmove(c_mx1, _mx1, ffi.sizeof(c_mx1))
    ffi.memmove(c_mx2, _mx2, ffi.sizeof(c_mx2))

    # 声明函数
    ffi.cdef('''
    int *mxmul(int nrow, int nk, int ncol, int *mx1, int *mx2);
    ''')

    # 调用dll库
    try :
    # 64位计算机加载mxmul.dll
    C = ffi.dlopen('mxmul.dll')
    except :
    # 32位计算机加载mxmul32.dll
    C = ffi.dlopen('mxmul32.dll')

    # 调用动态链接库中的函数
    c_res = C.mxmul(c_nrow, c_nk, c_ncol, c_mx1, c_mx2)

    #解包返回
    return ffi.unpack(c_res, nrow * ncol
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 用来测试效果的python程序    test.py
    from cmxmul import cmxmul

    if __name__ == '__main__':
    import time

    nrow, nk, ncol = 500, 300, 500
    mx1 = [[i for i in range(nk)] for j in range(nrow)]
    mx2 = [[i for i in range(ncol)] for j in range(nk)]
    start = time.perf_counter()
    rst = cmxmul(nrow, nk, ncol, mx1, mx2)
    end = time.perf_counter()
    print('运行时间:%.4f秒' % (end - start))

    '''
    运行时间:0.3135秒
    '''
  • Cython

    实现python扩展的一种语言,第三方库。

    https://cython.org/

    通过一种简单的语言来实现python和C的接口

    采用了Pyrex语法形式

    采用C数据类型的python编程,实现混合编程

  • SWIG

    一个将C/C++与脚本语言相整合的编译器,独立工具

    http://www.swig.org/

    通过一个编译器来实现python和C的接口

    纯C/C++编程,通过编写接口变成python模块

    独立C和python编程,重点在于编写接口(描述)

调用:python和C间以进程级别相互调用

模块间功能互用

以功能使用为目标

C/C++和python都是独立程序

方式:子进程或线程方式

  • Python 中调用C语言程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* 待调用的c程序源码 */
    #include<stdio.h>
    #include<stdlib.h>

    int main(int argc, char **argv)
    {
    int a;
    a = atoi(argv[1]);
    printf("input x:%d\n", a);
    printf("pow(x):%d\n", a*a);
    return 0;
    }
    //编译成pow.exe
    1
    2
    3
    4
    5
    6
    7
    # xxx.py
    import subprocess
    subprocess.run(["pow.exe", "9"])
    '''执行后的结果
    input x:9
    pow(x):81
    '''

    subprocess.run()的参数可以是一个字符串或一个列表

    如果是字符串,就是程序的名称

    如果是列表,是程序名称和参数组成的列表

  • C语言中调用python程序

    1
    2
    3
    4
    # pow.py
    import sys
    print("input x:%d"%sys.argv[1])
    print("pow(x):%d"%pow(int(sys.argv[1]),2))
    1
    2
    3
    4
    5
    6
    7
    8
    /* test.c */
    #include<stdio.h>
    #include<stdlib.h>
    int main(){
    system("python pow.py 9");
    return 0;
    }
    //编译后运行

    system(char *cmd) 是C/C++下的标准函数,C89定义。将指令和参数以字符指针形式作为参数传递执行程序。

嵌入:C/C++中调用python程序

利用python高产

引入python成熟功能库

以C/C++为主程序,python通过源文件形式使用

方式:python/c API https://docs.python.org/3/c-api/

  • 嵌入python语句:嵌入一个或多个python语句
  • 嵌入python脚本:嵌入一个或多个python文件
  • python/C API 需要加载python解释器以及加载python语句和脚本

头文件:python.h

函数:加载python解释器、嵌入python语句及脚本、数据类型转换等。

1
2
3
4
5
6
7
8
#include <Python.h>
int main()
{
Py_Initialize();
PyRun_SimpleString("print(eval('1 + 2'))");
Py_Finalize();
return 0;
}
函数 描述
Py_Initialize() 初始化Python解释器,加载builtins、__main__、sys等
Py_Finalize() 终结化python解释器,释放解释器占用的内存
PyRun_SimpleString(const char* cmd) 在__main__模块中执行一句语句 如果main不存在则创建
PyRun_SimpleFile(FILE fp, const char fname) 在C中调用一个Python文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# include<Python.h>

int main()
{
char fname[] = "mxmul.py";
FILE *fp = fopen(fname, 'r');

if (fp == NULL)
{
printf("fopen error");
return 1;
}
Py_Initialize();
PyRun_SimpleFile(fp, fname);
Py_Finalize();
return 0;

}